package com.ywu.standard.gateway.handler.user;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.ywu.boot.config.BootConfigAppConfig;
import com.ywu.boot.context.YwuContext;
import com.ywu.boot.context.YwuContextHolder;
import com.ywu.boot.context.YwuContextTenant;
import com.ywu.boot.context.YwuContextUser;
import com.ywu.common.constant.CommonConstant;
import com.ywu.common.result.CommonResult;
import com.ywu.common.utils.AesUtils;
import com.ywu.standard.gateway.config.AppConfig;
import com.ywu.standard.gateway.handler.user.impl.UserHandlerInterface;
import com.ywu.standard.gateway.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName UserTokenHandler
 * @Description TODO
 * @Author GroundDemo
 * @Date 2024/5/26 11:38
 * @Version 1.0
 **/
@Component
@Slf4j
@Order(1)
public class UserTokenHandler implements UserHandlerInterface {
    private static final String USER_TOKEN_REDIS_KEY = "YWU-ADMIN-USER:USER-TOKEN:";

    private static final String GUEST_ACCOUNT = "guest";

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private BootConfigAppConfig bootConfigAppConfig;

    @Override
    public Object handle(ServerWebExchange exchange) {
        log.debug("UserTokenHandler begin");
        MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
        String currentPath = exchange.getRequest().getURI().getPath();
        String[] split = appConfig.getExcludePath().split(",");
        HashSet<String> excludes = new HashSet<>(Arrays.asList(split));
        if (excludes.contains(currentPath)) {
            // 无需校验，返回空，上层设置无需校验状态
            return null;
        }
        List<HttpCookie> accessToken = cookies.get("AccessToken");
        // 如果请求头中不携带token，则表示以游客的身份进行访问
        if (CollectionUtils.isEmpty(accessToken)) {
            setUserAccount(GUEST_ACCOUNT, "");
            return exchange;
        }
        // 如果请求头中的Cookie不存在，则返回异常
        if (accessToken.size() > 1) {
            return CommonResult.error().setCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR).setErrorCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR_MESSAGE)
                    .setMessage("用户登录失效，请重新登录");
        }
        String decryptTokenKey;
        try {
            decryptTokenKey = AesUtils.decrypt(bootConfigAppConfig.getTokenKey(), bootConfigAppConfig.getSecretKey());
        } catch (Exception e) {
            log.error("loginUser error {}", e.getMessage());
            return CommonResult.error().setCode(CommonConstant.USER_TOKEN_PARSE_ERROR).setErrorCode(CommonConstant.USER_TOKEN_PARSE_ERROR_MESSAGE)
                    .setMessage("用户账号异常!");
        }
        // 解析Token
        try {
            Map<String, String> tokenInfoMap = JwtUtils.decodeToken(accessToken.get(0).getValue(), decryptTokenKey);
            String userAccount = tokenInfoMap.get("authentications");
            String tenantId = tokenInfoMap.get("tenantId");
            // 校验当前账号是否处在登录状态
            if (!checkRedisUserToken(userAccount)) {
                return CommonResult.error().setCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR).setErrorCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR_MESSAGE)
                        .setMessage("用户已登出");
            }
            // 将用户账号设置到上下文中
            setUserAccount(userAccount, tenantId);
        } catch (TokenExpiredException e) {
            // 解析RefreshToken
            List<HttpCookie> refreshToken = cookies.get("RefreshToken");
            if (CollectionUtils.isEmpty(refreshToken) || refreshToken.size() > 1) {
                log.error("TokenExpiredException error {}", e.getMessage());
                return CommonResult.error().setCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR).setErrorCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR_MESSAGE)
                        .setMessage("用户登录失效，请重新登录");
            }
            try {
                Map<String, String> tokenInfoMap = JwtUtils.decodeToken(refreshToken.get(0).getValue(), decryptTokenKey);
                String tenantId = tokenInfoMap.get("tenantId");
                String userAccount = tokenInfoMap.get("authentications");
                // 校验当前账号是否处在登录状态
                if (!checkRedisUserToken(userAccount)) {
                    return CommonResult.error().setCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR).setErrorCode(CommonConstant.USER_TOKEN_EXPIRE_ERROR_MESSAGE)
                            .setMessage("用户已登出");
                }
                // 重新生成token并设置cookie
                String newAccessToken = JwtUtils.createToken(userAccount, tenantId, decryptTokenKey, appConfig.getAccessExpire());
                String newRefreshToken = JwtUtils.createToken(userAccount, tenantId, decryptTokenKey, appConfig.getRefreshExpire());
                ResponseCookie accessTokenCookie = ResponseCookie.from("AccessToken", newAccessToken).path("/").build();
                ResponseCookie refreshTokenCookie = ResponseCookie.from("RefreshToken", newRefreshToken).path("/").build();
                // 重新向redis中设置值
                refreshRedisInfo(newRefreshToken, userAccount);
                exchange.getResponse().addCookie(accessTokenCookie);
                exchange.getResponse().addCookie(refreshTokenCookie);
                // 将用户账号设置到上下文中
                setUserAccount(userAccount, tenantId);
            } catch (TokenExpiredException ex) {
                log.error("TokenExpiredException error {}", e.getMessage());
                return CommonResult.error().setCode(CommonConstant.USER_TOKEN_PARSE_ERROR).setErrorCode(CommonConstant.USER_TOKEN_PARSE_ERROR_MESSAGE)
                        .setMessage("用户登录失效，请重新登录");
            }
        }
        return exchange;
    }

    /**
    * @Author GroundDemo
    * @Description 重新向redis中设置用户的token
    * @Date 14:37 2024/6/2
    * @Param [newRefreshToken, userAccount]
    * @return void
    **/
    private void refreshRedisInfo(String newRefreshToken, String userAccount) {
        String redisKey = USER_TOKEN_REDIS_KEY + userAccount;
        redisTemplate.opsForValue().set(redisKey, newRefreshToken);
        redisTemplate.expire(redisKey, appConfig.getRefreshExpire(), TimeUnit.MILLISECONDS);
    }

    /**
    * @Author GroundDemo
    * @Description 设置用户账号至上下文
    * @Date 11:59 2024/5/26
    * @Param [userAccount]
    * @return void
    **/
    private void setUserAccount(String userAccount, String tenantId) {
        YwuContext currentContext = YwuContextHolder.getCurrentContext();
        if (Objects.isNull(currentContext.getYwuContextUser())) {
            YwuContextUser ywuContextUser = new YwuContextUser();
            currentContext.setYwuContextUser(ywuContextUser);
        }
        currentContext.getYwuContextUser().setUserAccount(userAccount);
        YwuContextTenant ywuContextTenant = new YwuContextTenant();
        ywuContextTenant.setTenantId(tenantId);
        currentContext.setYwuContextTenant(ywuContextTenant);
    }

    /**
    * @Author GroundDemo
    * @Description 校验当前redis中是否存在该账号的token
    * @Date 14:34 2024/6/2
    * @Param [userAccount]
    * @return boolean
    **/
    private boolean checkRedisUserToken(String userAccount) {
        String redisKey = USER_TOKEN_REDIS_KEY + userAccount;
        return !Boolean.FALSE.equals(redisTemplate.hasKey(redisKey));
    }
}
